//
//  playback.h
//  GCDBenchmark
//
//  Created by 郭晓龙 on 2025/10/21.
//

#ifndef playback_h
#define playback_h

#include <unistd.h>
#include <iostream>
#include <fstream>
#include <map>
#include <functional>
#include <os/log.h>
#include <sys/stat.h>
#include "util.h"
#include "pattern.h"
//#include <chrono>

//inline void stall_us(size_t running_us, useconds_t sleep_us = 0)
//{
//    // running half of running_us firstly
//    auto start = std::chrono::steady_clock::now();
//    size_t passed = 0;
//    while (passed < running_us / 2) {
//        passed =
//            std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
//    }
//    
//    usleep(sleep_us);
//    
//    passed = 0;
//    start = std::chrono::steady_clock::now();
//    while (passed < running_us / 2) {
//        passed =
//            std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
//    }
//}
//
//inline void instr_exec(uint64_t instructions)
//{
//    // each loop cost 3 instructions
//    for (uint64_t i = 0; i < instructions; i += 3);
//}
//
//using stamp = std::chrono::time_point<std::chrono::high_resolution_clock>;
//
//static inline auto now()
//{
//    return std::chrono::high_resolution_clock::now();
//}
//
//static inline uint64_t ns(const stamp& base)
//{
//    return uint64_t(std::chrono::duration_cast<std::chrono::nanoseconds>(now() - base).count());
//}

struct queue_info {
    std::string type; // sq, cq
    uint64_t id;
    uint64_t concurrency;
};

struct exec_info {
    std::string type;
    uint64_t duration;
    dispatch_qos_class_t qos;
};

struct task_info_ {
    std::string type; // nm, uv, sq, cq
    uint64_t id;
    dispatch_qos_class_t qos;
    uint64_t queue;
    uint64_t submit;
    std::vector<exec_info> exec;
};

inline void read_case_from_trace_playback(std::ifstream& file, std::vector<queue_info>& queues, std::vector<task_info_>& tasks)
{
    os_log_t logger = os_log_create("com.ufogxl.GCDBenchmark", "playback");
    if (!file.is_open()) {
        os_log_error(logger, "File is not open!");
        return;
    }

    std::string line;
    size_t line_count = 0;
    while (std::getline(file, line)) {
        line_count++;
        if (line_count <= 5) {
            os_log_info(logger, "Line %zu: prefix check", line_count);
        }
        if (line.empty()) {
            continue;
        }

        size_t pos = line.find(',');
        if (pos == std::string::npos) {
            if (line_count <= 5) {
                os_log_info(logger, "Line %zu: no comma found, line='%s'", line_count, line.substr(0, 50).c_str());
            }
            continue;
        }
        std::string prefix = line.substr(0, pos);
        if (line_count <= 5) {
            os_log_info(logger, "Line %zu: prefix='%s'", line_count, prefix.c_str());
        }
        line = line.substr(pos + 1);

        if (prefix == "queue"){
            // |type|id|concurrency|
            queue_info info;
            pos = line.find(',');
            info.type = line.substr(0, pos);
            line = line.substr(pos + 1);

            pos = line.find(',');
            info.id = atol(line.substr(0, pos).c_str());
            line = line.substr(pos + 1);

            pos = line.find(',');
            info.concurrency = atol(line.substr(0, pos).c_str());
            line = line.substr(pos + 1);

            queues.push_back(info);
        }

        if (prefix == "task") {
            // |type|id|qos|queue|submit|exec|{type|duration}...|
            task_info_ info;
            
            pos = line.find(',');
            info.type = line.substr(0, pos);
            line = line.substr(pos + 1);
            
            pos = line.find(',');
            info.id = atol(line.substr(0, pos).c_str());
            line = line.substr(pos + 1);
            
            pos = line.find(',');
            info.qos = QoSGenerator(atoi(line.substr(0, pos).c_str()), true);
            line = line.substr(pos + 1);
            
            pos = line.find(',');
            info.queue = atol(line.substr(0, pos).c_str());
            line = line.substr(pos + 1);
            
            pos = line.find(',');
            info.submit = atol(line.substr(0, pos).c_str());
            line = line.substr(pos + 1);
            
            while (line.size() > 1) {
                exec_info exec;
                
                exec.qos = info.qos;
                
                pos = line.find(',');
                exec.type = line.substr(0, pos);
                line = line.substr(pos + 1);
                
                pos = line.find(',');
                exec.duration = atol(line.substr(0, pos).c_str());
                line = line.substr(pos + 1);
             
                info.exec.push_back(exec);
            }
            
            tasks.push_back(info);
        }
    }
}

static void task_func(const task_info_& task_info)
{
    for (auto& exec : task_info.exec) {
        if (exec.type == "Running") { // mock as cpu running
            cpu_intensive_task(exec.duration);
        }
        if (task_info.type != "uv") {
            if (exec.type == "Sleeping") { // mock as sleeping
                io_intensive_task(exec.duration);
            }
            if (exec.type == "CoWait") { // mock as cowait and then cowake
                cpu_intensive_task(exec.duration); // avoid thread switch to block state
                uint64_t tsc = arm64_cnt_vct();
                dispatch_async(dispatch_get_global_queue(exec.qos, 0), ^{
                    TRACE_COUNT("cnt_delay", arm64_cnt_vct() - tsc);
                });
            }
        }
    }
}

inline void run_playback(const std::string& filepath, size_t sleep_cost = 0)
{
    os_log_t logger = os_log_create("com.ufogxl.GCDBenchmark", "playback");

    std::ifstream file(filepath);

    if (!file.is_open()) {
        os_log_error(logger, "File open failed!");
        return;
    }

    // Load playback data from file
    std::vector<queue_info> queues;
    std::vector<task_info_> tasks;
    read_case_from_trace_playback(file, queues, tasks);

    std::map<uint64_t, std::unique_ptr<SerialQueue>> serial_queues;
    std::map<uint64_t, std::unique_ptr<ConcurrentQueue>> concurrent_queues;
    
    dispatch_group_t group = dispatch_group_create();
    
    for (auto& info : queues) {
        if (info.type == "sq") {
            serial_queues[info.id] = std::make_unique<SerialQueue>(QOS_CLASS_DEFAULT);
        }
        if (info.type == "cq") {
            concurrent_queues[info.id] = std::make_unique<ConcurrentQueue>(QOS_CLASS_DEFAULT, info.concurrency);
        }
    }

    for (uint64_t i = 0; i < tasks.size(); i++) {
        if (i > 0) {
            //sleep until next submit
            usleep(uint32_t(tasks[i].submit - tasks[i - 1].submit));
        }

        if (tasks[i].type == "nm" || tasks[i].type == "uv") {
            uint64_t tsc = arm64_cnt_vct();
            dispatch_group_async(group, dispatch_get_global_queue(tasks[i].qos, 0), ^{
                TRACE_COUNT("cnt_delay", arm64_cnt_vct() - tsc);
                task_func(tasks[i]);
            });
        }

        if (tasks[i].type == "sq") {
            serial_queues[tasks[i].queue]->Submit(std::bind(task_func, std::ref(tasks[i])), tasks[i].qos);
        }

        if (tasks[i].type == "cq") {
            concurrent_queues[tasks[i].queue]->Submit(std::bind(task_func, std::ref(tasks[i])), tasks[i].qos);
        }
    }
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    for (auto& [id, sq] : serial_queues) {
        sq->Wait();
    }
    
    for (auto& [id, cq] : concurrent_queues) {
        cq->Wait();
    }
    
    sleep(1);
    
//    exit(0);
}


#endif /* playback_h */
